package frontend;

import java.util.*;

public class SyntaxTreeNodeUtil {
	
	static public String getUserFuncReturnType(SyntaxTreeNode userFunc) {
		return userFunc.getFirstChild()  // signature node
			.getFirstChild().getValue().toString();
	}
	
	static public String getUserFuncName(SyntaxTreeNode userFunc) {
		return userFunc.getFirstChild().getValue().toString();
	}
	
	/* Get the argument type list of a user-defined function syntax node */
	static public ArrayList<String> getUserFuncArgList(SyntaxTreeNode userFunc) {
		SyntaxTreeNode sig = userFunc.getFirstChild();
		ArrayList<String> args = new ArrayList<String>();
		if (sig.getChildrenCount() < 2) {
			return args;
		} else {
			SyntaxTreeNode argNode = sig.getChildren().get(1); // args node
			for (SyntaxTreeNode n : argNode.getChildren()) {
				
				args.add(n.getFirstChild().getValue().toString());
			}
			return args;
		}
	}
	// Take Indicator node and return argslist
	static public ArrayList<String> getArgsList(SyntaxTreeNode node) {
		ArrayList<String> args = new ArrayList<String>();
		if(node.getChildrenCount() == 0)
			return args;
		else{
			SyntaxTreeNode listnode = node.getFirstChild();
			for (SyntaxTreeNode n : listnode.getChildren()) {	
				args.add(getNodeValueType(n));
			}
			return args;			
		}
	}
	/* Determine whether a node's type is compatible with the given type */
	static public Boolean isTypeCompatible(
			String typeName, SyntaxTreeNode node) {
		switch(node.getType()) {
		case LITSTRING: 
		case STRING:
			if (typeName.equals("string")) return true;
			break;
		case NUM:
		case INT:
			if (typeName.equals("int") || typeName.equals("float")) return true;
			break;
		case TF:
			if (typeName.equals("bool")) return true;
			break;
		case ID:
		case OP: 
		case LOGICAL:
		case FUNC:
		case INDICATOR:
			String t = getNodeValueType(node);
			return isTypeCompatible(typeName, t);
		default:
			return false;
		}
		return false;
	}
	
	static public Boolean isTypeCompatible(String t1, String t2) {
		if (t1.length()==0 || t2.length()==0) {
			return false;
		}
		else if (t1.equals("string")) {
			return t2.equals("string");
		}
		else if (t1.equals("bool")) {
			return t2.equals("bool");
		}
		else if (t1.equals("int") || t1.equals("float")) {
			return (t2.equals("int") || t2.equals("float"));
		}
                else if (t1.equals("void")) {
                        return t2.equals("void");
                }
		else {
			return false;
		}
	}
	
	static public String TranslateFromJavaType(String java_type) {
		if (java_type.equals("double"))
			return "float";
		if (java_type.equals("java.lang.String") || java_type.equals("String"))
			return "string";
		if (java_type.equals("java.lang.Boolean") || java_type.equals("Boolean"))
			return "bool";
		return java_type;
	}
	
	// Get the value type for a node. Return empty string on error
	static public String getNodeValueType(SyntaxTreeNode node) {
		if (node.getValueType().length() > 0)
			return node.getValueType();
		
		String type;
		String value = node.getValue().toString();
		
		switch(node.getType()) {
		case ARGUMENT:
			String arg_type = node.getFirstChild().getStringValue();
			node.setValueType(arg_type);
			break;
		case LITSTRING: 
		case STRING:
			node.setValueType("string");
			break;		
		case NUM:
			node.setValueType("float");
			break;
		case INT: 
			node.setValueType("int");
			break;
		case TF:
			node.setValueType("bool");
			break;
		case ID: // find type info for identifier in symbol table
			type = SemanticAnalyzer.currentST.getVariableType(value);
			if (type.length()==0) {
				SemanticAnalyzer.errs.addError("Symbol '" + value + "' is not defined"
						, node.getLineNumber());
			} else {
				node.setValueType(type);
			}
			break;
		case CROSSOP:
			generateCROSSOPTypeInfo(node);			
			break;
		case FUNC: // find function return type in symbol table
			String funcname = node.getValue().toString();
			if(funcname.startsWith("Property.")){  //Check property first name in stock
				String stockname = node.getFirstChild().getFirstChild().getValue().toString();
				if(SemanticAnalyzer.rootST.stocknameTable.get(stockname)==null){
					int line = node.getFirstChild().getFirstChild().getLineNumber();
					SemanticAnalyzer.errs.addError(new CompileError("NameError: no stock names with " +stockname + ".", line));
				}
			}
		case INDICATOR:
			type = SemanticAnalyzer.rootST.getFunctionReturnType(value.toString());
			ArrayList<String> argslist = SemanticAnalyzer.rootST.getFunctionArgList(value.toString());
			if (type.length()==0) {
				SemanticAnalyzer.errs.addError("function '" + value + "' is not defined"
						, node.getLineNumber());
			} else {
				// Check args to see if this Indicator match the args list				
				ArrayList<String> nodeargs = getArgsList(node);
				if(argslist.equals(nodeargs)){
					node.setValueType(type);
				}
				else{
					SemanticAnalyzer.errs.addError("function '" + value + "' arguments are incorrect"
							, node.getLineNumber());
				}
				
			}
			break;
		case OP: 
			generateOPTypeInfo(node);
			break;
		case LOGICAL:
			generateLogicalOpTypeInfo(node);
			break;
		case PREDEFINED:
			String variable = node.getValue().toString();
			if(variable.equals("sector")){
				node.setValueType("string");
			}
			else{
					SyntaxTreeNode parent = node;
					do{ 
						parent = parent.getParent();
					}
					while( parent.getType()!= SyntaxTreeNode.Type.SELLSTRAT 
							&& parent.getType()!= SyntaxTreeNode.Type.BUYSTRAT
							&& parent.getType() != SyntaxTreeNode.Type.USERDEFINE
							&& parent != null);
					if( variable.equals("loss") || variable.equals("profit") ){
						if(parent.getType()==SyntaxTreeNode.Type.BUYSTRAT)
							SemanticAnalyzer.errs.addError(
									new CompileError("AttributeError: wrong predefined variable " +
											"in buy program.", node.getLineNumber()));	
					}
					else if(variable.equals("rise")||variable.equals("fall")){
						if(parent.getType()==SyntaxTreeNode.Type.SELLSTRAT)
							SemanticAnalyzer.errs.addError(
									new CompileError("AttributeError: wrong predefined variable" +
											" in sell program.", node.getLineNumber()));	
					}
				
				node.setValueType("float");				
			}
			
			break;
		case PRINT:
			generatePrintTypeInfo(node);
			break;
		case LIST:
			generateListTypeInfo(node);
			break;
		default:
			//System.out.println(node.getType());
			break;
		}
		return node.getValueType();
	}
	
	static private String generateOPTypeInfo(SyntaxTreeNode node) {
		String op = node.getValue().toString();
		String type;
		
		// the only unary operator
		if (op.equals("NEG")) {
			type = getNodeValueType(node.getFirstChild());
			if (type.length()==0) {
				return ""; // error happens in child
			}
			else if (!isNumericalType(type)) {
				SemanticAnalyzer.errs.addError("unary operator '-' cannot be applied to '" 
						+ type + "' type", node.getLineNumber());
				return "";
			} 
			else {
				node.setValueType(type);
				return type;
			}
		} 
		else { // binary operator
			String ltype = getNodeValueType(node.getChild(0));
			String rtype = getNodeValueType(node.getChild(1));
			
			// error in child
			if (ltype.length()==0 || rtype.length()==0) { 
				return "";
			}
			
			// Arithmetic operator
			if (isArithOperator(op)) {
				// Ensure both operands are numerical values
				if (isNumericalType(ltype) && isNumericalType(rtype)) {
					if (ltype.equals("int") && rtype.equals("int")) node.setValueType("int");
					else node.setValueType("float");
					return node.getValueType();
				} else {
					SemanticAnalyzer.errs.addError("Both sides of the operator '"
							+ op + "' need to be numerical types", node.getLineNumber());
					return "";
				}
			} 
			// >, >=, <, <=
			else if (isCompareOperator(op)) {
				// Ensure both operands are numerical values
				if (isNumericalType(ltype) && isNumericalType(rtype)) {
					node.setValueType("bool");
					return "bool";
				} else {
					SemanticAnalyzer.errs.addError("Both sides of the operator '"
							+ op + "' need to be numerical types", node.getLineNumber());
					return "";
				}
			} 
			// ==, !=
			else if (isEqualityOperator(op)) {
				if ((ltype.equals("string") && rtype.equals("string"))
					|| ltype.equals("bool") && rtype.equals("bool")
					|| isNumericalType(ltype) && isNumericalType(rtype)) {
					node.setValueType("bool");
					return "bool";
				} else {
					SemanticAnalyzer.errs.addError("Left and right operands of '"
							+ op + "' are not compatible", node.getLineNumber());
					return "";
				}
			}
			// other unexpected operators
			else {
				SemanticAnalyzer.errs.addError("Unexpected operator '"
						+ op + "'.", node.getLineNumber());
				return "";
			}
		}
	}
	static private String generateListTypeInfo(SyntaxTreeNode node){
		for(SyntaxTreeNode child: node.getChildren())
		{
			getNodeValueType(child);
		}
		node.setValueType("bool");
		return "bool";
	}
	
	static private String generatePrintTypeInfo(SyntaxTreeNode node) {
		SyntaxTreeNode listnode = node.getFirstChild();
		node.setValueType("bool");
		if(listnode != null)
			getNodeValueType(listnode);
		return "bool";
	}
	static private String generateCROSSOPTypeInfo(SyntaxTreeNode node) {
		String op = node.getValue().toString();
		String ltype = getNodeValueType(node.getChild(0));
		String rtype = getNodeValueType(node.getChild(1));
		node.setValueType("bool");
		return "bool";
	}	
	static private String generateLogicalOpTypeInfo(SyntaxTreeNode node) {
		String op = node.getValue().toString();
		
		// unary logical operator 'not'
		if (op.equals("not")) {
			String type = getNodeValueType(node.getFirstChild());
			// error in child
			if (type.length()==0) return "";
			else if (!type.equals("bool")){
				SemanticAnalyzer.errs.addError("unary logical operator 'not' cannot "
						+ "be applied to '" + type + "' type", node.getLineNumber());
				return "";
			} else {
				node.setValueType("bool");
				return "bool";
			}
		} 
		//binary logical operator 
		else {
			String ltype = getNodeValueType(node.getChild(0));
			String rtype = getNodeValueType(node.getChild(1));
			
			// error in child
			if (ltype.length()==0 || rtype.length()==0) { 
				return "";
			} 
			// Make sure both left/right operands are of bool type
			else if (!ltype.equals("bool") || !rtype.equals("bool")){
				SemanticAnalyzer.errs.addError("Both left and right operands of "
						+ "logical operator '" + op + "' must be of 'bool' type"
						, node.getLineNumber());
				return "";
			} else {
				node.setValueType("bool");
				return "bool";
			}
		}
	}
	
	static public Boolean isArithOperator(String op) {
		return (op.equals("+") 
				|| op.equals("-")
				|| op.equals("*")
				|| op.equals("/"));
	}
	
	static public Boolean isCompareOperator(String op) {
		return (op.equals(">")
				|| op.equals("<")
				|| op.equals(">=")
				|| op.equals("<="));
	}
	
	static public Boolean isEqualityOperator(String op) {
		return (op.equals("==") || op.equals("!="));
	}
	
	static public Boolean isNumericalOnlyOperator(String op) {
		return (op.equals("+")
				|| op.equals("-")
				|| op.equals("*")
				|| op.equals("/")
				|| op.equals(">")
				|| op.equals("<")
				|| op.equals(">=")
				|| op.equals("<="));
	}
	
	static public Boolean isNumericalType(String t) {
		return (t.equals("float") || t.equals("int"));
	}
	
	static public Boolean isDerivableFromExpression(SyntaxTreeNode.Type t) {
		switch (t) {
		case OP:
		case LOGICAL:
		case ID:
		case NUM:
		case INT:
		case STRING:
		case FUNC:
		case INDICATOR:
		case CROSSOP:
		case LITSTRING:
		case TF:
		case PREDEFINED:
			return true;
		default:
			return false;
		}
	}
}
